/**
 * 
 * Description:
 *  Stores information about an image.
 *
 * 	Code based of same code provided by Prof. Grewe.
 */

import java.awt.*;
import java.io.*;
import java.awt.image.*;
import javax.imageio.*;

/**
 * Contains information about the Image
 * (size-#rows, #columns, format, filename)
 * as well as the image data itself.
 *
 */
public class ImageData
{
	private ImageData parentImageData;

	/** 
	 * Pixel data
	 */
	private int width;
	private int height;
	private int [] pixelData;

	/**
	 * This is used for Java to render our pixels.
	 */
	private BufferedImage renderImage;
	// Boni: I don't think this needs to be saved.  Let me know if it should.
	//private BufferedImage newImage;
	// Boni: This doesn't seem to be used
	//private BufferedImage destImage;
	

	//private ROI roi;
	// Boni: I don't think we should update the render image directly,
	// otherwise the pixel data will be out of sync.
	public void setImageData(BufferedImage renderImage)
	{
		renderImage = this.renderImage;
	}

	// Create IPImage directly from an Image object.
	// Used when loading an image file.
	public ImageData(String filename)
	{
		//roi = null;
		parentImageData = null;

		// Attempt to read in the file to a temp buffered image.
		BufferedImage newImage = null;
		try {
			newImage = ImageIO.read(new File(filename));
		} catch (IOException e) {
			p.print("Caught exception: " + e);
			return;
		}

		if (newImage == null)
		{
			return;
		}
	
		// Extract image info.
		width = newImage.getWidth();
		height = newImage.getHeight();

		// Convert this read in image to have an int rgb pixel format,
		// and render the new image onto this buffered image.
		renderImage = new BufferedImage(width, height,
				BufferedImage.TYPE_INT_RGB);
		Graphics2D g = renderImage.createGraphics();
		g.drawImage(newImage, null, 0, 0);
		g.dispose();

// Ningyi add		
		// Boni: not used anywhere
		//destImage = new BufferedImage(width, height,
		//		BufferedImage.TYPE_INT_RGB);
		
// Ningyi add finish		

		// Allocate and grab pixel data from our render image.
		pixelData = new int[width * height];
		getRenderPixels(pixelData, 0, 0, width, height);
	}

	// Create IPImage from another IPImage
	public ImageData(ImageData src, int x, int y, int w, int h)
	{
		parentImageData = src;
		//roi = new ROI(x, y, w, h);

		// Copy pixel data (or subregion of pixel data).
		width = w;
		height = h;
		p.print("creating array size: " + width*height);
		pixelData = new int[width * height];
		src.getRenderPixels(pixelData, x,y,width, height);

		// Now setup our render image and set our pixels.
		renderImage = new BufferedImage(width, height,
				BufferedImage.TYPE_INT_RGB);
		setRenderPixels(pixelData, 0, 0, width, height);

		p.print("creating ipimage from another ipimage: " + 
				x + ", " + y + ", " +
				width + ", " + height);
		p.print(renderImage + "");
	}

	public int getWidth() { return width; }
	public int getHeight() { return height; }
	public ImageData getParentImage() { return parentImageData; }

	/**
	 * Get a copy of our pixel data.
	 * Pixel data will be copied into the 1d array.
	 * It is assumed data is an int array of
	 * IPImage.getWidth() * IPImage.getHeight() length.
	 * Assumes the following:
	 * - data array size is w*h
	 * - data array stride is w.
	 */
	public void getPixels(int [] data, 
			int startx, int starty, int w, int h)
	{
		//System.arraycopy(pixelData, 0, data, 0, pixelData.length);
		p.print("here getting pixels");
		int stride = width;
		int x, y;
		for (y = 0; y < h; y++)
		{
			for (x = 0; x < w; x++)
			{
				int dstOffset = y*w + x;
				int srcOffset = (y+starty) * stride
					+ (x + startx);
				data[dstOffset] = pixelData[srcOffset];
			}
		}
	}

	/**
	 * Set the pixel data.
	 * Assumes the following:
	 * - data array size is w*h
	 * - data array stride is w.
	 */
	public void setPixels(int [] data,
			int startx, int starty, int w, int h)
	{
		//System.arraycopy(data, 0, pixelData, 0, pixelData.length);
		int stride = width;
		int x, y;
		for (y = 0; y < h; y++)
		{
			for (x = 0; x < w; x++)
			{
				int srcOffset = y*w + x;
				int dstOffset = (y+starty) * stride
					+ (x + startx);
				pixelData[dstOffset] = data[srcOffset];
			}
		}

		// We should always update our render image after setting
		// pixels explicitly.
		setRenderPixels(pixelData, 0, 0, width, height);
	}

	/**
	 * Extract pixels from our render image (BufferedImage).
	 * This should only be used internally.
	 */
	private void getRenderPixels(int [] data, int x, int y,
			int w, int h)
	{
		// We assume scanline of array is always the width.
		renderImage.getRGB(x, y, w, h, data, 0, w);
	}

	private void setRenderPixels(int [] data, 
			int x, int y, int w, int h)
	{
		// We assume scanline of array is always the width.
		renderImage.setRGB(x, y, w, h, data, 0, w);
	}

	public BufferedImage getBufferedImage()
	{
		return renderImage;
	}
	
	// TODO: this seems same as getBufferedImage() above
	public Image getImage()
	{
		return renderImage;
	}

	/**
	 * Returns true if the given x, y coordinate is
	 * valid for this image.
	 */
	public boolean isValidCoord(int x, int y)
	{
		if (x < 0 || x >= getWidth() ||
			y < 0 || y >= getHeight())
		{
			return false;
		}
		else
		{
			return true;
		}
	}

	public void saveImage(String filename)
	{
		// Only saving as jpg for now.
		p.print("Saving: " + filename);
		try {
			ImageIO.write(renderImage, "jpg", new File(filename));
		} catch (IOException io) {
			p.print("An error occred: " + io);
		}
	}

	public void paint(Graphics g, int left, int top)
	{
		if (renderImage != null)
		{
			Graphics2D g2 = (Graphics2D)g;
			g2.drawImage(renderImage, null, left, top);
		}
		else
		{
			p.print("Render Image is null");
		}
	}

	ImageData threshold(int tvalue) {
		// Get pixels
		int[] pixels = allocPixels();
		getPixels(pixels, 0, 0,
				width, height);
	
		// Modify pixels.
		for (int y=0; y < height; y++)
		{
			for (int x=0; x < width; x++)
			{
				// Extract rgb
				int pixel = pixels[y*width + x];
				int r = Util.getR(pixel);
				int g = Util.getG(pixel);
				int b = Util.getB(pixel);
	
				// Perform threshold individually on each color
				// component.
				if (r < tvalue) { r = 0; } else { r = 255; }
				if (g < tvalue) { g = 0; } else { g = 255; }
				if (b < tvalue) { b = 0; } else { b = 255; }
	
				
				pixel = Util.setRGB(r, g, b);
				pixels[y*width + x] = pixel;
			}
		}
		ImageData result = clone();
		result.setPixels(pixels, 0, 0,
				width, height);
		return result;
	}

	int[] allocPixels() {
		int [] pixels = new int[width * height];
		return pixels;
	}

	protected ImageData clone() {
		return new ImageData(this, 0, 0,
				getWidth(),
				getHeight());
	}
}

